package com.niiwoo.civet.trade.service.local.kaola;

import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.fastjson.JSON;
import com.google.common.base.Preconditions;
import com.lycheepay.gateway.client.dto.QueryTradeRecordResultDTO;
import com.lycheepay.gateway.client.dto.TradeRecord;
import com.lycheepay.gateway.client.dto.TradeResultDTO;
import com.lycheepay.gateway.client.dto.gbp.TreatyRecordResponseDTO;
import com.niiwoo.civet.account.dto.common.BankCardDTO;
import com.niiwoo.civet.account.enums.CanRefundEnum;
import com.niiwoo.civet.account.service.bankcard.BankCardDubboService;
import com.niiwoo.civet.trade.constant.RedissonLockConstant;
import com.niiwoo.civet.trade.dao.entity.*;
import com.niiwoo.civet.trade.dao.mapper.KaolaRepayOrderMapperExt;
import com.niiwoo.civet.trade.dao.mapper.KaolaWithholdOrderMapperExt;
import com.niiwoo.civet.trade.dao.mapper.ProjectContractMapperExt;
import com.niiwoo.civet.trade.dto.PrepaymentAmountDTO;
import com.niiwoo.civet.trade.dto.request.kaola.KaoLaLiCaiRepayRequestDTO;
import com.niiwoo.civet.trade.dto.response.kaola.KaoLaLiCaiRepayResponseDTO;
import com.niiwoo.civet.trade.enums.FadadaContractEnum;
import com.niiwoo.civet.trade.enums.RepayStatusEnum;
import com.niiwoo.civet.trade.enums.kaola.*;
import com.niiwoo.civet.trade.service.local.kaola.newrepay.KaoLaLiCaiApplyForKFTService;
import com.niiwoo.tripod.base.component.PrivacyMasks;
import com.niiwoo.tripod.provider.exception.BizException;
import com.niiwoo.tripod.service.component.SnowflakeIdWorker;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;

/**
 * 考拉还款服务.
 *
 * @author lichuanzhi
 * @date 2017-12-28 10:42
 */

@Slf4j
@Service
public class KaoLaLiCaiRepayOrderService {

    @Autowired
    private KaolaRepayOrderMapperExt kaolaRepayOrderMapper;
    @Autowired
    private KaolaWithholdOrderMapperExt kaolaWithholdOrderMapper;
    @Autowired
    private ProjectContractMapperExt projectContractMapperExt;

    @Autowired
    private KaoLaLiCaiRepaymentService repaymentService;
    @Autowired
    private KaoLaLiCaiApplyForKFTService applyForKFTService;
    @Autowired
    private LycheePayRequestService payRequestService;

    @Reference(version = "1.0.0")
    private BankCardDubboService bankCardDubboService;

    @Autowired
    private SnowflakeIdWorker snowflakeIdWorker;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private PrivacyMasks privacyMasks;

    public KaoLaLiCaiRepayResponseDTO repayment(KaoLaLiCaiRepayRequestDTO dto, KaoLaRepayOrderRepayTypeEnum repaymentTypeEnum, KaoLaLiCaiIsPassiveEnum isPassiveEnum) {
        return repayment(dto, repaymentTypeEnum, isPassiveEnum, null);
    }

    public KaoLaLiCaiRepayResponseDTO repayment(KaoLaLiCaiRepayRequestDTO dto, KaoLaRepayOrderRepayTypeEnum repaymentTypeEnum, KaoLaLiCaiIsPassiveEnum isPassiveEnum, ProjectBorrowerSummary summaryParam) {
        log.info("考拉标的:{},{}还款##开始，KaoLaLiCaiRepayRequestDTO={}",dto.getProjectId(), isPassiveEnum == KaoLaLiCaiIsPassiveEnum.REPAY_ACTIVE ? "主动" : "代扣", JSON.toJSONString(dto));
        Long projectId = dto.getProjectId();
        ProjectBorrowerSummary summary = Optional.ofNullable(summaryParam).orElse(repaymentService.getProjectBorrowerSummary(projectId));

        //koala的所有还款操作都是互斥的有且只能有一个正常触发
        RLock lock = redissonClient.getLock(RedissonLockConstant.KAOLA_REPAY.concat(dto.getUserId()).concat(RedissonLockConstant
                .KEY_NS_DELIMIT).concat(String.valueOf(projectId)));
        KaolaWithholdOrder withholdOrder;
        try {
            if (!lock.tryLock()) {
                throw new BizException("TRD21001");
            }
            checkStatus(summary, repaymentTypeEnum);
            withholdOrder = getSelfProxy().createRepayOrder(summary, repaymentTypeEnum, isPassiveEnum);
            TradeResultDTO tradeResult;
            try {
                tradeResult = payRequestService.collectFromBankAccount(withholdOrder, summary.getProjectId());
            } catch (Exception e) {
                log.error("考拉标的:{},还款##异常", projectId, e);
                this.handleException(withholdOrder, e);
                throw new BizException("TRD20006");
            }
            CompletableFuture.runAsync(new NotifyRepaymentTask(withholdOrder, tradeResult));
        } finally {
            lock.unlock();
        }

        return repaymentService.getResponseForApp(summary, withholdOrder.getAmount(), withholdOrder.getReqNo());
    }

    @Transactional(rollbackFor = Exception.class)
    public KaolaWithholdOrder createRepayOrder(ProjectBorrowerSummary summary, KaoLaRepayOrderRepayTypeEnum repaymentTypeEnum, KaoLaLiCaiIsPassiveEnum isPassiveEnum) {
        BigDecimal repayAmount;
        if (repaymentTypeEnum == KaoLaRepayOrderRepayTypeEnum.REPAY_SETTLE) {
            PrepaymentAmountDTO prepaymentAmount = repaymentService.calcNiiWooPreRePay(summary, false);
            repayAmount = prepaymentAmount.getPrincipal().add(prepaymentAmount.getInterest())
                    .add(prepaymentAmount.getPlatformManagerFee()).add(prepaymentAmount.getCreditGuaranteeFee());
        } else {
            repayAmount = repaymentService.getImmediateRepayAmount(summary);
        }

        //组装还款订单
        KaolaRepayOrder repayOrder = new KaolaRepayOrder();
        long repayOrderId = snowflakeIdWorker.nextId();
        Date currentDate = new Date();
        repayOrder.setId(repayOrderId);
        repayOrder.setRepayAmount(repayAmount);
        repayOrder.setRepayNo(summary.getCurrentPeriod());
        repayOrder.setProjectId(summary.getProjectId());
        repayOrder.setGenerateSource(isPassiveEnum.getCode());
        repayOrder.setRepayType(repaymentTypeEnum.getValue());
        repayOrder.setRepayStatus(KaolaRepayOrderRepayStatusEnum.WITH_HOLDING.getValue());
        repayOrder.setCreateTime(currentDate);
        repayOrder.setUpdateTime(currentDate);
        kaolaRepayOrderMapper.insertSelective(repayOrder);

        return createKaolaWithholdOrder(summary, repayAmount, repayOrderId);
    }

    private KaolaWithholdOrder createKaolaWithholdOrder(ProjectBorrowerSummary summary, BigDecimal repayAmount, long repayOrderId) {

        //组装代扣订单
        KaolaWithholdOrder withholdOrder = new KaolaWithholdOrder();
        withholdOrder.setId(snowflakeIdWorker.nextId());
        withholdOrder.setRepayOrderId(repayOrderId);
        withholdOrder.setOrderNo(String.valueOf(snowflakeIdWorker.nextId()));
        withholdOrder.setReqNo(UUID.randomUUID().toString());

        //获取用户银行卡信息
        BankCardDTO bankCardDTO = Optional.ofNullable(bankCardDubboService.getUserBankCardInfo(summary.getBorrowerUserId())).orElseThrow(
                () -> new IllegalStateException("用户银行卡信息不存在"));
        withholdOrder.setAmount(repayAmount);
        withholdOrder.setCustBankAccountNo(privacyMasks.encryptPrivacy(bankCardDTO.getBankAccountNo()));
        withholdOrder.setCustBankName(bankCardDTO.getBankType().getBankName());
        withholdOrder.setCustBankNo(BankCode2CardBinEnum.enumOf(bankCardDTO.getBankCode()).getBankNo());
        withholdOrder.setCustBindPhoneNo(privacyMasks.encryptPrivacy(bankCardDTO.getMobileNo()));
        withholdOrder.setCustId(privacyMasks.encryptPrivacy(bankCardDTO.getIdCard()));
        withholdOrder.setCustName(bankCardDTO.getRealName());
        Date currentDate = new Date();
        withholdOrder.setTradeTime(currentDate);
        withholdOrder.setCreateTime(currentDate);
        withholdOrder.setUpdateTime(currentDate);
        withholdOrder.setCustProtocolNo(getProtocolNo(summary, withholdOrder));
        withholdOrder.setRemark(getRemark(summary));
        kaolaWithholdOrderMapper.insertSelective(withholdOrder);
        return withholdOrder;
    }

    private String getRemark(ProjectBorrowerSummary summary) {
        return "借款项目【" + summary.getProjectId() + "】第{" +
                summary.getCurrentPeriod() + "/" + summary.getTotalPeriod() +
                "期}还款";
    }

    private String getProtocolNo(ProjectBorrowerSummary summary, KaolaWithholdOrder withholdOrder) {

        //获取历史订单协议号
        String protocolNo = kaolaWithholdOrderMapper.selectProtocolNoByAccountNo(withholdOrder.getCustBankAccountNo());
        if (Objects.isNull(protocolNo)) {
            try {

                //获取合同编号
                String merchantTreatyNo = projectContractMapperExt.queryByPageWithUserAndType(summary.getBorrowerUserId(),
                        (byte)14, FadadaContractEnum.TradeTypeEnum.LOAN.getType(), 0, 1).stream()
                        .findFirst()
                        .map(ProjectContract::getContractNo)
                        .orElse("N" + String.valueOf(snowflakeIdWorker.nextId()));
                log.info("考拉标的:{},代扣合同号:{},userId:{}", summary.getProjectId(), merchantTreatyNo, summary.getBorrowerUserId());

                protocolNo = Optional.ofNullable(payRequestService.sendTreatyRecord2KFT(withholdOrder, merchantTreatyNo))
                        .filter(r -> TreatyRecordStatusEnum.enumOf(r.getStatus()) == TreatyRecordStatusEnum.EFFECT)
                        .map(TreatyRecordResponseDTO::getTreatyNo)
                        .orElseThrow(() -> new IllegalStateException("有效协议备案编号获取失败"));
            } catch (Exception e) {
                throw new RuntimeException("协议备案编号获取异常",e);
            }
        }
        return protocolNo;
    }

    private void checkStatus(ProjectBorrowerSummary summary, KaoLaRepayOrderRepayTypeEnum repaymentTypeEnum) {
        Byte repayStatus = summary.getRepayStatus();
        switch (repaymentTypeEnum) {
            case REPAY_NOMAL:
                Preconditions.checkArgument(repayStatus.equals(RepayStatusEnum.NORMAL.getCode()), "**正常还款**标的还款状态错误，RepayStatus:{%s}", repayStatus);
                break;
            case REPAY_OVERDUE:
                Preconditions.checkArgument(repayStatus.equals(RepayStatusEnum.OVERDUE.getCode())
                        || repayStatus.equals(RepayStatusEnum.BADLOANS.getCode()), "**逾期还款**标的还款状态错误，RepayStatus:{%s}", repayStatus);
                break;
            case REPAY_SETTLE:
                Preconditions.checkArgument(repayStatus.equals(RepayStatusEnum.NORMAL.getCode()), "**提前还款**标的还款状态错误，RepayStatus:{%s}", repayStatus);

                //还款日当天，借款人立即还款成功后，限制借款人当天再操作全额还款。借款人点击全额还款时，弹窗文案：您已还本期，请次日再操作全额还款，谢谢.
                if(repaymentService.repayDayRefundAndReRePay(summary)){
                    throw new BizException("TRD21002","");
                }
                break;
            default:
        }

        //异常,原因是数据库中有一笔正常还款请求正在处理中
        if(CanRefundEnum.REFUNDING == isProcessing(summary.getProjectId())){
            throw new BizException("TRD21001");
        }

        //检查如果已经代扣成功但还款失败，也需阻止再次生产代扣订单，避免重复代扣
        Optional.ofNullable(kaolaRepayOrderMapper.selectWithholdSucc(summary)).filter(count -> count == 0)
                .orElseThrow(() -> new BizException("TRD21001"));
    }

    public CanRefundEnum isProcessing(Long projectId) {
        return kaolaRepayOrderMapper.countProcessingRepay(projectId) > 0 ? CanRefundEnum.REFUNDING : CanRefundEnum.CAN_REFUND;
    }

    public Byte getRepayStatus(Long projectId, Integer currentPeriod) {
        KaolaRepayOrder repayOrder = kaolaRepayOrderMapper.selectLastRepayOrderByProjectIdAndRepayNo(projectId, currentPeriod);
        KaoLaRealRepayment.NiiwooStatusEnum niiwooStatusEnum = null;
        if (Objects.nonNull(repayOrder)) {
            niiwooStatusEnum = KaoLaRealRepayment.NiiwooStatusEnum.IN_HAND;
            KaolaRepayOrderRepayStatusEnum repayStatusEnum = KaolaRepayOrderRepayStatusEnum.enumOf(repayOrder.getRepayStatus());
            if (repayStatusEnum == KaolaRepayOrderRepayStatusEnum.REPAY_FAIL) {
                niiwooStatusEnum = KaoLaRealRepayment.NiiwooStatusEnum.REPAY_FAILED;
            } else if (repayStatusEnum == KaolaRepayOrderRepayStatusEnum.REPAY_SUCCESS) {
                niiwooStatusEnum = KaoLaRealRepayment.NiiwooStatusEnum.REPAY_SUCCESS;
            }
        }
        return Optional.ofNullable(niiwooStatusEnum).map(KaoLaRealRepayment.NiiwooStatusEnum::getStatus).orElse(null);
    }

    private void handleResponse(KaolaWithholdOrder withholdOrder, TradeResultDTO tradeResult) {
        QueryTradeRecordResultDTO  recordResultDTO = new QueryTradeRecordResultDTO();
        List<TradeRecord> details = new ArrayList<>();
        TradeRecord tradeRecord = new TradeRecord();
        tradeRecord.setErrorCode(tradeResult.getErrorCode());
        tradeRecord.setFailureDetails(tradeResult.getFailureDetails());
        tradeRecord.setStatus(String.valueOf(tradeResult.getStatus()));
        tradeRecord.setBankReturnTime(tradeResult.getBankReturnTime());
        details.add(tradeRecord);
        recordResultDTO.setDetails(details);
        applyForKFTService.notifyRepayment(withholdOrder, recordResultDTO);
    }


    @Getter
    @Setter
    @AllArgsConstructor
    private class NotifyRepaymentTask implements Runnable{

        private KaolaWithholdOrder withholdOrder;
        private TradeResultDTO tradeResultDTO;

        @Override
        public void run() {
            KaoLaLiCaiRepayOrderService.this.handleResponse(withholdOrder, tradeResultDTO);
        }
    }


    private void handleException(KaolaWithholdOrder withholdOrder, Exception e){
        KaolaWithholdOrder updateWithholdOrder = new KaolaWithholdOrder();
        updateWithholdOrder.setId(withholdOrder.getId());
        updateWithholdOrder.setStatus(Byte.valueOf(QueryTradeRecordResultStatusEnum.EXCEPTION_FAIL.getCode()));
        updateWithholdOrder.setErrorCode("KFT9999");
        updateWithholdOrder.setFailureDetails(Optional.ofNullable(e.getMessage()).map(r -> StringUtils.substring(r, 0, 100)).orElse("服务器开小差了"));
        kaolaWithholdOrderMapper.updateByPrimaryKeySelective(updateWithholdOrder);

        KaolaRepayOrder repayOrder = new KaolaRepayOrder();
        repayOrder.setId(withholdOrder.getRepayOrderId());
        repayOrder.setRepayStatus(KaolaRepayOrderRepayStatusEnum.REPAY_FAIL.getValue());
        repayOrder.setRemark("服务器开小差了");
        kaolaRepayOrderMapper.updateByPrimaryKeySelective(repayOrder);
    }

    private KaoLaLiCaiRepayOrderService getSelfProxy() {
        return (KaoLaLiCaiRepayOrderService) AopContext.currentProxy();
    }

}
